﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LightCAD.MathLib;
using XY = LightCAD.MathLib.Vector2;

namespace LightCAD.MathLib
{
    public class ClipperWrapper
    {
        public List<List<XY>> Subjects { get; set; }
        public List<List<XY>> SubOpenPaths { get; set; }
        public List<List<XY>> Clips { get; set; }
        public PolyFillType SubjFillType { get; set; } = PolyFillType.pftNonZero;
        public PolyFillType ClipFillType { get; set; } = PolyFillType.pftNonZero;
        private Clipper clipper = null;
        public PolyTree Polytree { get; set; }
        public int ScaleVal { get; private set; } = (int)(1E+5);
        private Vector2 basePoint = null;

        public ClipperWrapper()
        {
            this.Subjects = new List<List<XY>>();
            this.SubOpenPaths = new List<List<XY>>();
            this.Clips = new List<List<XY>>();
            this.Polytree = null;
        }
        public ClipperWrapper(int scale) : this()
        {
            if (scale > 0)
                this.ScaleVal = scale;
        }
        public void Set(List<List<XY>> shapes)
        {
            this.Subjects.Clear();
            this.Subjects.AddRange(shapes);
        }

        public void AddSubJectOpenPaths(List<List<XY>> openPaths)
        {
            for (int i = 0; i < openPaths.Count; i++)
            {
                AddSubJectOpenPath(openPaths[i]);
            }
        }
        public void AddSubJectOpenPath(List<XY> openPath)
        {
            this.SubOpenPaths.Add(openPath.ToListEx());
        }

        public void SetClips(List<List<XY>> shapes)
        {
            this.Clips.Clear();
            this.Clips.AddRange(shapes);
        }

        public void Execute(ClipType cmd)
        {
            if (this.clipper == null)
                this.clipper = new Clipper();
            else
                this.clipper.Clear();
            //计算范围盒子，用盒子中心点作为原点
            var box = new Box2();
            this.Subjects.ForEach(ps => ps.ForEach(p => box.ExpandByPoint(new Vector2(p.X, p.Y))));
            this.SubOpenPaths.ForEach(ps => ps.ForEach(p => box.ExpandByPoint(new Vector2(p.X, p.Y))));
            this.Clips.ForEach(ps => ps.ForEach(p => box.ExpandByPoint(new Vector2(p.X, p.Y))));
            this.basePoint = box.GetCenter();
            this.basePoint.X = Math.Round(this.basePoint.X);
            this.basePoint.Y = Math.Round(this.basePoint.Y);
            this.clipper.AddPaths(Vec2sListToIntPointsList(this.Subjects, ScaleVal), PolyType.ptSubject, true);
            clipper.AddPaths(Vec2sListToIntPointsList(this.SubOpenPaths, ScaleVal), PolyType.ptSubject, false);
            this.clipper.AddPaths(Vec2sListToIntPointsList(this.Clips, ScaleVal), PolyType.ptClip, true);

            this.Polytree = new PolyTree();
            this.clipper.Execute(cmd, this.Polytree, SubjFillType, ClipFillType);

        }

        public IntPoint Vec2ToIntPoint(XY bP, int ScaleVal)
        {
            return new IntPoint(Math.Round((bP.X - this.basePoint.X) * ScaleVal), Math.Round((bP.Y - this.basePoint.Y) * ScaleVal));
        }
        public List<IntPoint> Vec2sToIntPoints(List<XY> bmdPs, int ScaleVal)
        {
            var intPs = new List<IntPoint>();
            foreach (var bmdP in bmdPs)
            {
                intPs.Add(Vec2ToIntPoint(bmdP, ScaleVal));
            }
            return intPs;
        }
        public List<List<IntPoint>> Vec2sListToIntPointsList(List<List<XY>> pointsList, int ScaleVal)
        {
            var result = new List<List<IntPoint>>();
            for (int i = 0; i < pointsList.Count; i++)
                result.Add(Vec2sToIntPoints(pointsList[i], ScaleVal));
            return result;
        }
        public XY IntPointToVec2(IntPoint intP, int ScaleVal)
        {
            double x = (double)intP.X / (double)ScaleVal + this.basePoint.X;
            double y = (double)intP.Y / (double)ScaleVal + this.basePoint.Y;
            var xy = new XY();
            xy.Set(x, y);
            return xy;
        }
        public List<XY> IntPointsToVec2s(List<IntPoint> intPoints, int ScaleVal)
        {
            return intPoints.Select(ip => IntPointToVec2(ip, ScaleVal)).ToListEx();
        }
        public List<List<XY>> IntPointsListToVec2sList(List<List<IntPoint>> intPointsList, int ScaleVal)
        {
            return intPointsList.Select(ips => IntPointsToVec2s(ips, ScaleVal)).ToListEx();
        }

        public List<ClipperShape> GetShapes(double tolerence)
        {
            if (this.Polytree == null)
            {
                var shapesResults = new List<ClipperShape>();
                for (int i = 0; i < this.Subjects.Count; i++)
                {
                    var currSubject = this.Subjects[i];
                    var shapeResult = new ClipperShape(currSubject);//没有clips直接返回subjects
                    shapesResults.Add(shapeResult);

                }
                return shapesResults;
            }
            var expolygons = ClipperLibExt.PolyTreeToExPolygons(this.Polytree, ExPolygonType.EvenOdd, (int)(tolerence * ScaleVal));

            return ExPolygonsToClipperShapes(expolygons, ScaleVal);
        }
        public List<ClipperShape> ExPolygonsToClipperShapes(List<ExPolygon> expolygons, int ScaleVal)
        {
            var shapes = new List<ClipperShape>();
            for (var i = 0; i < expolygons.Count; i++)
            {
                var exPoly = expolygons[i];
                var shape = ExPolygonToBmdShape(exPoly, ScaleVal);
                shapes.Add(shape);
            }
            return shapes;
        }
        public ClipperShape ExPolygonToBmdShape(ExPolygon exPolygon, int ScaleVal)
        {
            var newOuter = IntPointsToBmdPoints(exPolygon.Outer, ScaleVal);
            var shape = new ClipperShape(newOuter);
            shape.IsOpen = exPolygon.IsOpen;
            for (var j = 0; j < exPolygon.Children.Count; j++)
            {
                var hole = exPolygon.Children[j];
                var holeShape = ExPolygonToBmdShape(hole, ScaleVal);
                shape.Holes.Add(holeShape);
            }
            return shape;
        }
        public XY IntPointToBmdPoint(IntPoint intP, int ScaleVal)
        {
            var p = new XY();
            p.Set((double)intP.X / (double)ScaleVal + +this.basePoint.X, (double)intP.Y / (double)ScaleVal + this.basePoint.Y);
            return p;
        }
        public List<XY> IntPointsToBmdPoints(List<IntPoint> intPoints, int ScaleVal)
        {
            return intPoints.Select(ip => IntPointToBmdPoint(ip, ScaleVal)).ToList();
        }
    }

    public enum ExPolygonType
    {
        Tree,
        EvenOdd
    }
    public static class ClipperLibExt
    {
        public static List<ExPolygon> PolyTreeToExPolygons(this PolyTree Polytree, ExPolygonType exPolygonType, int tolerence)
        {
            var expolygons = new List<ExPolygon>();

            var childs = Polytree.Childs;
            for (int i = 0; i < childs.Count; i++)
            {
                var node = childs[i];
                if (exPolygonType == ExPolygonType.Tree)
                    expolygons.Add(PolyNodeToExPolygonsTree(node, tolerence));
                else if (exPolygonType == ExPolygonType.EvenOdd)
                    expolygons.Add(PolyNodeToExPolygonsEvenOdd(node, expolygons, tolerence));
            }
            return expolygons;
        }

        public static ExPolygon PolyNodeToExPolygonsTree(this PolyNode polynode, int tolerence)
        {
            var ep = new ExPolygon();
            ep.Outer = Clipper.CleanPolygon(polynode.Contour, tolerence);
            ep.IsOpen = polynode.IsOpen;
            var childs = polynode.Childs;
            var ilen = childs.Count;
            ep.Children = new List<ExPolygon>(ilen);
            for (int i = 0; i < ilen; i++)
            {
                var child = childs[i];
                var childExtPolygon = PolyNodeToExPolygonsTree(child, tolerence);
                ep.Children.Add(childExtPolygon);
            }
            return ep;
        }
        /// <summary>
        /// 按照奇偶填充方式
        /// </summary>
        /// <param name="polynode"></param>
        /// <param name="Polys"></param>
        /// <param name="tolerence"></param>
        /// <returns></returns>
        public static ExPolygon PolyNodeToExPolygonsEvenOdd(this PolyNode polynode, List<ExPolygon> Polys, int tolerence)
        {

            var ep = new ExPolygon();
            if (!polynode.IsOpen)
                ep.Outer = Clipper.CleanPolygon(polynode.Contour, tolerence);
            else
                ep.Outer = polynode.Contour;
            ep.IsOpen = polynode.IsOpen;
            var childs = polynode.Childs;
            var ilen = childs.Count;
            ep.Children = new List<ExPolygon>();
            for (int i = 0; i < ilen; i++)
            {
                var child = childs[i];
                var childExtPolygon = PolyNodeToExPolygonsEvenOdd(child, Polys, tolerence);
                if (polynode.IsHole == false)
                    ep.Children.Add(childExtPolygon);
                else
                    Polys.Add(childExtPolygon);
            }
            return ep;
        }
    }
    public class ExPolygon
    {
        public List<IntPoint> Outer { get; set; }
        public bool IsOpen { get; set; } = false;
        public List<ExPolygon> Children { get; set; }
    }

    public class ClipperShape
    {
        private readonly List<XY> points = new List<XY>();
        private List<ClipperShape> holes = new List<ClipperShape>();

        public List<XY> Points => points;
        public List<ClipperShape> Holes { get => holes; }

        public bool IsOpen { get; set; } = false;
        public ClipperShape(List<XY> ps)
        {
            this.points.AddRange(ps.Select(p =>
            {
                var np = new XY();
                np.Set(p.X, p.Y);
                return np;

            }));
        }
    }
}
